package cn.xiaobai.config;

import cn.xiaobai.domain.RedisConfInfo;
import cn.xiaobai.listener.FileChangeListener;
import cn.xiaobai.listener.PushChannelChangeListener;
import cn.xiaobai.monitor.MonitorServer;
import cn.xiaobai.handler.redis.RedisPushDataHandler;
import cn.xiaobai.utils.SpringContextUtils;
import cn.xioabai.common.constant.JsonStrConstant;
import cn.xioabai.common.utility.IOUtils;
import cn.xioabai.common.utility.JsonFileUtils;
import cn.xioabai.common.utility.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.HiddenFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.List;
import java.util.Map;

import static cn.xiaobai.domain.ConfInfo.*;

/**
 * 文件监听器工厂
 *
 * @Author yangdaji
 * @Date 2022/11/7 0007
 * @PackageName xiaobai.config
 * @ClassName FileListenerFactory
 */
@Slf4j
@Component
public class FileListenerFactory {
    @Autowired
    private ApplicationArguments applicationArguments;

    /**
     * 监控服务
     */
    @Autowired
    private MonitorServer monitorServer;

    @Autowired
    private RedisPushDataHandler redisPushDataHandler;


    /**
     * 运行参数名称
     */
    @Value("${args.name:}")
    private String argsName;

    /**
     * 监听目录
     */
    @Value("${conf.path:}")
    private String confPath;


    // 限制文件数量
    @Value("${file-limit.push-file-number:30}")
    private Integer pushFileNumber;

    @Value("${file-limit.channel-file-number:2}")
    private Integer channelFileNumber;

    /**
     * 获取文件变更监控
     *
     * @return
     */
    @Bean
    public FileAlterationMonitor getPushFileMonitor() {
        FileChangeListener fileChangeListener = SpringContextUtils
                .getBean(FileChangeListener.class);
        //创建监听器
        return createFileAlterationMonitor(listenerFile, null
                , suffixFile, interval, fileChangeListener);
    }

    /**
     * 获取推送频道监控
     *
     * @return
     */
    @Bean
    public FileAlterationMonitor getPushChannelMonitor() {
        //创建监听器
        String suffix = null;
        if (StringUtils.isNotBlank(listenerChannelFileName)) {
            suffix = IOUtils.getFileExtension(listenerChannelFileName);
        }
        // 获取注入对象
        PushChannelChangeListener pushChannelChangeListener = SpringContextUtils
                .getBean(PushChannelChangeListener.class);
        return createFileAlterationMonitor(listenerChannelFile,
                listenerChannelFileName
                , suffix, interval, pushChannelChangeListener);
    }

    /**
     * 创建文件监控
     *
     * @param directory                     监听目录
     * @param fileName                      文件名称,如果为空则认为是目录
     * @param suffix                        监听文件类型
     * @param interval                      监听周期
     * @param fileAlterationListenerAdaptor 监听器
     * @return
     */
    private FileAlterationMonitor createFileAlterationMonitor(String directory, String fileName
            , String suffix, Long interval, FileAlterationListenerAdaptor fileAlterationListenerAdaptor) {
        IOFileFilter filter = createFileFilter(directory, fileName, suffix);

        // 将前置过滤器注册到观察者中, 作用于过滤指定文件
        FileAlterationObserver observer = new FileAlterationObserver(directory, filter);
        //添加自定义监听
        observer.addListener(fileAlterationListenerAdaptor);

        //返回监听者,
        return new FileAlterationMonitor(interval, observer);
    }

    /**
     * 创建文件过滤器
     *
     * @param directoryOrFilePath 目录或文件路径
     * @param suffix              文件类型
     * @param fileName            文件名称
     * @return
     */
    private IOFileFilter createFileFilter(String directoryOrFilePath, String fileName, String suffix) {

        // 目标监听目录
        File fileDirectory = new File(directoryOrFilePath);
        //文件名称为空, 则使用的是目录
        IOFileFilter directoryFilter;
        IOFileFilter fileFilter;
        if (Strings.isBlank(fileName)) {
            //不存在则创建, 因为只会观察给定目录下的文件,不存在时不会报错
            if (!fileDirectory.exists()) {
                fileDirectory.mkdirs();
            }

            directoryFilter = FileFilterUtils.and(FileFilterUtils.directoryFileFilter(), HiddenFileFilter.VISIBLE);
            // 创建指定后缀文件过滤器, 或指定文件监听
            fileFilter = FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter(suffix));
        } else {
            // 如果对应的文件不存在则不做处理
            if (!fileDirectory.exists()) {
                return null;
            }
            directoryFilter = FileFilterUtils.and(FileFilterUtils.fileFileFilter(),
                    FileFilterUtils.nameFileFilter(fileName), HiddenFileFilter.VISIBLE);
            // 创建指定后缀文件过滤器, 或指定文件监听
            fileFilter = FileFilterUtils.or(FileFilterUtils.suffixFileFilter(suffix));
        }
        return FileFilterUtils.or(directoryFilter, fileFilter);
    }

    /**
     * 加载配置
     */
    private void loadConf() {
        String jsonConf;
        List<String> optionValues = applicationArguments.getOptionValues(argsName);
        if (CollectionUtils.isEmpty(optionValues)) {
            // 未收到传递的配置参数,则使用jar 下默认配置, 注: 修改只能修改jar内的文件
            log.info("未接收到外部参数, 使用默认配置: {}", confPath);
            jsonConf = JsonFileUtils.readConfig(confPath);
        } else {
            // 获取自定义外部路径
            String filePath = optionValues.get(0);
            log.info("收到传递参数: {}, 使用设置的路径: {}", optionValues, filePath);
            jsonConf = JsonFileUtils.readConfig(filePath);
        }
        setConf(jsonConf);
    }

    /**
     * 配置设置
     *
     * @param jsonConfData 读取的json 格式配置
     */
    private void setConf(String jsonConfData) {
        // 初始化配置信息
        Map<String, String> stringMap = initConfInfo(jsonConfData);
        RedisConfInfo.initRedisConfInfo(JsonUtils.toJsonStr(stringMap));
        log.info("监听的目录: {}, 文件类型: {}, 监听周期: {} 秒", listenerFile, suffixFile, interval / 1000);
        log.info("监听的频道映射文件路径: {}, 文件名称: {}, 监听周期: {} 秒", listenerChannelFile,
                listenerChannelFileName, interval / 1000);
    }

    /**
     * 启动初始化推送数据
     */
    @PostConstruct
    public void initPushData() {
        log.info("加载配置....");
        loadConf();
        // 推送数据单目录下限制 30个文件, 频道映射可以放置 2 个
        List<File> filePushList = IOUtils.scanFilePathLimit(listenerFile, JsonStrConstant.JSON_SUFFIX, pushFileNumber);
        List<File> fileChannelList = IOUtils.scanFilePathLimit(listenerChannelFile, IOUtils.getFileExtension(listenerChannelFileName)
                , channelFileNumber);
        log.info("初始化推送数据, 数量限制 30");
        monitorServer.initPushData(filePushList);
        log.info("初始化推送频道别名, 映射的别名数量: {}", fileChannelList.size());
        monitorServer.initChannel(fileChannelList);
        // 启动开始往redis推送
        redisPushDataHandler.startPush();
    }
}
